import {
    EditorSuggest,
    TFile,
    Editor,
    EditorPosition,
    EditorSuggestContext,
    EditorSuggestTriggerInfo,
    App, TFolder, Vault, HeadingCache, MarkdownView,
} from "obsidian";
import type HeadingSuggestPlugin from "src/main";
import {stripLeft} from "../utils";

interface IHeadingCompletion {
    label: string;
}

export default class HeadingSuggest extends EditorSuggest<IHeadingCompletion> {
    app: App;
    private plugin: HeadingSuggestPlugin;
    private upLevelHeadingSuggestions: Map<string, string[]>;

    constructor(app: App, plugin: HeadingSuggestPlugin) {
        super(app);
        this.app = app;
        this.plugin = plugin;
    }

    getSuggestions(context: EditorSuggestContext): IHeadingCompletion[] | Promise<IHeadingCompletion[]> {
        const suggestions = this.getHeadingSuggestions(context);
        if (suggestions.length) {
            return suggestions;
        }

        // catch-all if there are no matches
        return [{ label: context.query }];
    }

    getHeadingSuggestions(context: EditorSuggestContext): IHeadingCompletion[] {
        return this.getHeadings().filter((item) => {
            // return stripLeft(item, "#").trim().toLowerCase().startsWith(context.query);
            return stripLeft(item, "#").trim().toLowerCase().contains(context.query.trim().toLowerCase());
        }).map((val) => ({ label: val }));
    }

    renderSuggestion(suggestion: IHeadingCompletion, el: HTMLElement): void {
        el.setText(suggestion.label);
    }

    selectSuggestion(suggestion: IHeadingCompletion, evt: MouseEvent | KeyboardEvent): void {
        const { editor } = this.context;
        const selectedHeadingText = suggestion.label;
        const upLevelHeadingsText = this.upLevelHeadingSuggestions.get(selectedHeadingText);
        const mdView = this.app.workspace.getActiveViewOfType(MarkdownView);
        const content = mdView.data;
        let replacementWithUpLevel = selectedHeadingText;
        for (const upLevelHeadingText of upLevelHeadingsText) {
            if (content.indexOf(upLevelHeadingText) === -1) { // JM：表示现在文档中没有过这一层目录
                replacementWithUpLevel = upLevelHeadingText + "\n" + replacementWithUpLevel;
            }
        }
        editor.replaceRange(replacementWithUpLevel, this.context.start, this.context.end);
    }
    getUpLevelHeading(headingIndex: number, headingCandidates: HeadingCache[], headingChain: HeadingCache[]) {
        const currentHeading = headingCandidates[headingIndex];
        const level = currentHeading.level;
        if (level <= 2) {
            headingChain.push(currentHeading);
            return;
        }
        const upLevel = level - 1;
        for (let i = headingIndex; i--; i>= 0)
        {
            if (headingCandidates[i].level <= upLevel){ // 找到上一级heading
                headingChain.push(currentHeading);
                this.getUpLevelHeading(i, headingCandidates, headingChain);
                break;
            }
        }
    }
    getUpLevelHeadingRecursively(cachedHeadings: HeadingCache[]) : Map<HeadingCache, HeadingCache[]>{
        const upLevelHeadingsArray = cachedHeadings.map((item, i) => {
            const chain: HeadingCache[] = [];
            this.getUpLevelHeading(i, cachedHeadings, chain);
            return chain;
        });

        const upLevelHeadingsMap = new Map();
        for(const heading of upLevelHeadingsArray){
            upLevelHeadingsMap.set(heading[0], heading.slice(1));
        }
        return upLevelHeadingsMap;
    }
    getHeadings(): string[]{
        let headingSuggestions: string[] = [];

        const markdownFiles: TFile[] = [];
        const startLocation = this.plugin.settings.vaultStartLocation.trim();
        const folderOrFile = this.app.vault.getAbstractFileByPath(startLocation);
        if (folderOrFile instanceof TFile) {
            markdownFiles.push(folderOrFile);
        } else if (folderOrFile instanceof TFolder) {
            Vault.recurseChildren(folderOrFile, (file => {
                if (file instanceof TFile && file.extension === "md") {
                    markdownFiles.push(file);
                }
            }));
        }

        // const markdownFiles = this.app.vault.getMarkdownFiles();
        markdownFiles.sort((a, b) => {
            if (a.stat.ctime > b.stat.ctime) return -1;
            if (a.stat.ctime < b.stat.ctime) return 1;
            return 0;
        });

        // const upLevelHeadingSuggestions: Map<string, string> = new Map();
        this.upLevelHeadingSuggestions = new Map();
        markdownFiles.forEach((markFile: TFile) => {
            for (const ignoredSuffix of this.plugin.settings.ignoredSuffixArray) {
                if (markFile.path.endsWith(ignoredSuffix)) {
                    return null;
                }
            }
            if (this.app.metadataCache.getFileCache(markFile) && this.app.metadataCache.getFileCache(markFile).headings) {
                const cachedHeadings = this.app.metadataCache.getFileCache(markFile).headings;
                const upLevelHeadingsMap = this.getUpLevelHeadingRecursively(cachedHeadings);
                upLevelHeadingsMap.forEach((headingCacheArray, keyHeading) => {
                    const reversedArray = headingCacheArray.reverse();
                    const upLevelHeadingsText: string[] = reversedArray.map(v => "#".repeat(v.level) + ` ${v.heading}`);
                    this.upLevelHeadingSuggestions.set("#".repeat(keyHeading.level) + ` ${keyHeading.heading}`, upLevelHeadingsText);
                });
                // console.log(upLevelHeadingsMap);
                if (cachedHeadings){
                    headingSuggestions = headingSuggestions.concat(cachedHeadings.map((v) => {
                        // return  "#".repeat(v.level) + ` ${v.heading} (${markFile.path})`;
                        return  "#".repeat(v.level) + ` ${v.heading}`; // TODO: 最好加一个hint，但是去重要手动做
                    }));
                }
            }
        });
        headingSuggestions = headingSuggestions.filter(heading => { return !this.plugin.settings.ingoredHeaders.contains(heading); });
        return Array.from(new Set(headingSuggestions));
    }

    onTrigger(
        cursor: EditorPosition,
        editor: Editor,
        file: TFile
    ): EditorSuggestTriggerInfo {
        if (!this.plugin.settings.isAutosuggestEnabled) {
            return null;
        }

        const triggerPhrase = this.plugin.settings.autocompleteTriggerPhrase;
        const startPos = this.context?.start || {
            line: cursor.line,
            ch: cursor.ch - triggerPhrase.length,
        };

        if (!editor.getRange(startPos, cursor).startsWith(triggerPhrase)) {
            return null;
        }

        const precedingChar = editor.getRange(
            {
                line: startPos.line,
                ch: startPos.ch - 1,
            },
            startPos
        );

        // Short-circuit if `@` as a part of a word (e.g. part of an email address)
        // if (precedingChar && /[`a-zA-Z0-9]/.test(precedingChar)) {
        //     return null;
        // }

        // this.getHeadings();

        return {
            start: startPos,
            end: cursor,
            query: editor.getRange(startPos, cursor).substring(triggerPhrase.length),
        };
    }
}